package com.neborosoft.android.gui.adapters.listview;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
import com.neborosoft.lib.collections.Containers;
import com.neborosoft.lib.collections.ListItemGetter;
import com.neborosoft.lib.collections.algorithms.SortedListItemGetter;
import com.neborosoft.lib.handlers.Callback3;
import com.neborosoft.lib.utilities.Utilities;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * Created with IntelliJ IDEA.
 * User: CM
 * Date: 06.11.12
 * Time: 1:56
 * To change this template use File | Settings | File Templates.
 */
public abstract class ViewArrayAdapter<Element,ViewHolder> extends BaseAdapter {
    protected static final int NULL_VIEW_TYPE = 1;
    protected static final int NORMAL_VIEW_TYPE = 0;
    protected static final int VIEW_TYPES_COUNT = 2;

    private List<Element> elements;
    private int maxRequestedPosition = -1;
    private int receivedNullElementPosition = -1;
    private boolean isFirstViewMeasured = false;
    private ViewTreeObserver.OnGlobalLayoutListener onGlobalLayoutListener;

    private OnItemClick<Element> onItemClickListener;
    private OnNullElementReceived<Element> onNullElementReceivedListener;

    private AbsListView parent;

    private LayoutInflater inflater;

    private ListItemGetter<Element> elementGetter;

    protected ViewHolder getViewHolder(int position){
        View view = getView(position);
        if(view != null){
            return (ViewHolder)view.getTag();
        }
        return null;
    }

    public List<Element> getElements() {
        return elements;
    }

    public View getView(int position){
        if(parent.getAdapter() != this){
            return null;
        }

        int visiblePosition = parent.getFirstVisiblePosition();
        View view = parent.getChildAt(position - visiblePosition);

        return view;
    }

    public AbsListView getParentListView(){
        return parent;
    }

    public OnItemClick<Element> getOnItemClickListener() {
        return onItemClickListener;
    }

    public void setOnItemClickListener(OnItemClick<Element> onItemClickListener) {
        this.onItemClickListener = onItemClickListener;
    }

    public OnNullElementReceived<Element> getOnNullElementReceivedListener() {
        return onNullElementReceivedListener;
    }

    public void setOnNullElementReceivedListener(OnNullElementReceived<Element> listener) {
        this.onNullElementReceivedListener = listener;
    }

    protected int getMaxDisplayedNullElementsCount(){
        return 1;
    }

    @Override
    public int getCount() {
        if(elements == null){
            return 0;
        }

        if(receivedNullElementPosition < 0){
            return elements.size();
        }
        else {
            return receivedNullElementPosition + getMaxDisplayedNullElementsCount();
        }
    }

    @Override
    public Object getItem(int position) {
        return getElement(position);
    }

    @Override
    public long getItemId(int position) {
        return 0;
    }

    @Override
    public int getItemViewType(int position) {
        Element element = getElement(position);
        if(element == null){
            return NULL_VIEW_TYPE;
        }
        else {
            return NORMAL_VIEW_TYPE;
        }
    }

    @Override
    public int getViewTypeCount() {
        return VIEW_TYPES_COUNT;
    }

    protected void reuseNullView(final int position, View convertView){

    }

    protected void onFirstViewMeasured(ViewHolder viewHolder){

    }

    protected boolean isFirstViewMeasured(){
        return isFirstViewMeasured;
    }

    @Override
    public View getView(final int position, View convertView, ViewGroup viewGroup) {
        final Element element = getElement(position);

        if(element == null){
            if(onNullElementReceivedListener != null){
                onNullElementReceivedListener.onNull(this, Collections.unmodifiableList(elements), position);
            }
            receivedNullElementPosition = position;
            return getNullView(position, convertView);
        }

        ViewHolder viewHolder = null;
        if(convertView != null){
            viewHolder = (ViewHolder)convertView.getTag();
        }

        if(viewHolder == null){
            convertView = inflater.inflate(getRootLayoutId(),null);
            viewHolder = createViewHolder(convertView);
            convertView.setTag(viewHolder);
        }

        if(position > maxRequestedPosition){
            onNewElementRequestedForDisplay(element, position, convertView, viewHolder);
            maxRequestedPosition = position;
        }

        reuseView(element,viewHolder,position);

        convertView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onItemClick(element,v,position);
            }
        });

        if(position == 0 && !isFirstViewMeasured){
            ViewTreeObserver viewTreeObserver = parent.getViewTreeObserver();
            final ViewHolder finalViewHolder = viewHolder;
            onGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    if(!isFirstViewMeasured &&
                            parent.getAdapter() == ViewArrayAdapter.this &&
                            elements != null && !elements.isEmpty())
                    {
                        isFirstViewMeasured = true;
                        onFirstViewMeasured(finalViewHolder);
                    }
                }
            };
            viewTreeObserver.addOnGlobalLayoutListener(onGlobalLayoutListener);
        }

        return convertView;
    }

    private View getNullView(int position, View convertView) {
        if(convertView == null){
            convertView = inflater.inflate(getNullLayoutId(),null);
        }

        reuseNullView(position, convertView);
        return convertView;
    }

    @Override
    public void notifyDataSetChanged() {
        receivedNullElementPosition = -1;
        super.notifyDataSetChanged();
    }

    @Override
    public void notifyDataSetInvalidated() {
        receivedNullElementPosition = -1;
        super.notifyDataSetInvalidated();
    }

    protected void onItemClick(Element element,View view,int position){
        if(onItemClickListener != null){
            onItemClickListener.onClick(element,view,position);
        }
    }

    protected abstract int getRootLayoutId();
    protected int getNullLayoutId(){
        throw new NullPointerException("null elements are not allowed");
    }
    protected abstract ViewHolder createViewHolder(View view);
    protected abstract void reuseView(Element element, ViewHolder viewHolder, int position);

    public final Element getElement(int index){
        if(elementGetter == null){
            return elements.get(index);
        } else {
            return elementGetter.getElement(index);
        }
    }

    public void setElements(Iterable<Element> elements){
        if(elements == null){
            throw new NullPointerException();
        }

        this.elements = Containers.iterableToList(elements);
        maxRequestedPosition = -1;
        isFirstViewMeasured = false;

        ViewTreeObserver viewTreeObserver = parent.getViewTreeObserver();
        viewTreeObserver.removeGlobalOnLayoutListener(onGlobalLayoutListener);

        notifyDataSetChanged();
    }

    public final void setElements(List<Element> elements){
        Iterable<Element> iterable = elements;
        setElements(iterable);
    }

    protected void onNewElementRequestedForDisplay(Element element, int position, View view,
                                                   ViewHolder viewHolder){

    }

    private void internalAddElement(Element element){
        elements.add(element);
    }

    public final void addElement(Element element){
        internalAddElement(element);
        notifyDataSetChanged();
    }

    public void addElements(Iterable<Element> elements){
        for(Element i : elements){
            internalAddElement(i);
        }
        notifyDataSetChanged();
    }

    public void onAdapterChanged(){
        setElements(Collections.<Element>emptyList());
    }

    public ViewArrayAdapter(Context context, AbsListView parent){
        if(parent == null){
            throw new NullPointerException();
        }

        this.parent = parent;
        inflater = LayoutInflater.from(context);
        Utilities.debugLogAllocation(this);
    }

    protected final void iterateVisibleItems(final Callback3<Element,Integer,ViewHolder> callback){
        if(parent.getAdapter() != this){
            return;
        }

        int first = parent.getFirstVisiblePosition();
        int last = parent.getLastVisiblePosition();

        for(int i = first; i <= last; i++){
            Element object = getElement(i);
            if(object == null){
                continue;
            }

            View view = parent.getChildAt(i);
            ViewHolder viewHolder = (ViewHolder)view.getTag();
            callback.call(object,i,viewHolder);
        }
    }

    protected boolean isVisible(int position){
        int first = parent.getFirstVisiblePosition();
        int last = parent.getLastVisiblePosition();

        return position <= last && position >= first;
    }

    public ListItemGetter<Element> getElementGetter() {
        return elementGetter;
    }

    public void setElementGetter(ListItemGetter<Element> elementGetter) {
        this.elementGetter = elementGetter;
        notifyDataSetChanged();
    }

    public void sort(){
        setElementGetter(new SortedListItemGetter<Element>(elements));
    }

    public void sort(Comparator<Element> comparator){
        setElementGetter(new SortedListItemGetter<Element>(elements, comparator));
    }

    @Override
    protected void finalize() throws Throwable {
        Utilities.debugLogDeallocate(this);
    }
}
